package ch.fhnw.apsi.mail;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Properties;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;

import com.sun.mail.smtp.SMTPTransport;

import ch.fhnw.apsi.beans.Firma;

public class Mailer {
	private final static String msgTemplate = "Hallo! Vielen Dank für Ihre Registrierung, hier Ihre Anmeldedaten:\n"
			+ "Benutzername: %s\n"
			+ "Kennwort: %s\n";
	private final static Properties mailProps = new Properties();
	static {
		mailProps.put("mail.smtp.host", "smtp.gmail.com");
		mailProps.put("mail.smtp.port", "587");
		mailProps.put("mail.transport.protocol","smtp");
		mailProps.put("mail.smtp.auth", "true");
		mailProps.put("mail.smtp.starttls.enable", "true");
		mailProps.put("mail.smtp.tls", "true");
	}
	
	private final static Authenticator auth = new Authenticator() {
		@Override
		protected javax.mail.PasswordAuthentication getPasswordAuthentication() {
			return new PasswordAuthentication("apsi.htmlform.martin.schwarz", "Ojangoh9luit");
		};
	};
	
	/**
	 * send a registration email
	 * @param u Firma (with email)
	 * @param username username of the new user
	 * @param passwordPlain the plaintext password of the new user
	 * @throws MailingException if the mail wasnt sent
	 */
	public static void sendRegistrationMail(
			@Nonnull final Firma u,
			@Nonnull final String username,
			@Nonnull final String passwordPlain) throws MailingException {
		Session mailSession = Session.getInstance(mailProps, auth);
		MimeMessage email = new MimeMessage(mailSession);
		try {
			email.setFrom("apsi.htmlform.martin.schwarz@gmail.com");
			email.setRecipients(Message.RecipientType.TO, InternetAddress.parse(u.getEmail()));
			email.setSubject("FHNW HtmlForm Registrierung");
			email.setText(String.format(msgTemplate, new Object[] {username, passwordPlain}));
			email.saveChanges();
			Transport.send(email);
		} catch (MessagingException e) {
			throw new MailingException(e);
		}
	}
	
	/**
	 * check if an email-address does definitely not exist
	 * @param email email address
	 * @return false, if sending an email to the address in u is impossible, true if it might work
	 */
	public static boolean emailMightExist(@Nonnull final String email) {
		final InternetAddress address;
		try {
			address = new InternetAddress(email);
			address.validate();
		} catch (AddressException e) {
			return false;
		}
		
		final String domain = extractDomain(address);
		if (domain == null) return false;
		final String mx = lookupMxRecord(domain);
		if (mx == null || !mxMightBeValid(mx)) return false;
		
		// thats all we can safely do...
		return true;
	}
	
	@CheckForNull
	private static String extractDomain(@Nonnull InternetAddress emailAddress) {
		final String address = emailAddress.getAddress();
		if (address == null) return null;
		final String[] parts = address.split("@");
		if (parts.length != 2) return null;
		return parts[1];
	}
	
	/**
	 * find first mail-handling-server for domain
	 * @param domainPart part after the @ from an email address (fo@o.bar.com --> o.bar.com)
	 * @return the mx-record, if at least one was found in dns, null otherwise
	 */
	@CheckForNull
	private static String lookupMxRecord(@Nonnull final String domainPart) {
	    final Lookup dnsLookup;
		try {
			dnsLookup = new Lookup(domainPart, Type.MX);
		} catch (TextParseException e) {
			return null;
		}
	    final Record[] records = dnsLookup.run();
	    if (records != null && records.length > 0) {
	    	return records[0].getName().toString();
	    }
	    return null;
	}
	
	/**
	 * checks if the given name resolves in dns and maps to an ip-address that
	 * could be used for mail-delivery (not local, not multicast)
	 * @param dnsName mx-record from dns
	 * @return true if the name resolves to a public-ip, false otherwise
	 */
	private static boolean mxMightBeValid(@Nonnull final String dnsName) {
		try {
			final InetAddress addr = InetAddress.getByName(dnsName);
			if (	addr.isSiteLocalAddress() ||
					addr.isLinkLocalAddress() ||
					addr.isLoopbackAddress() ||
					addr.isMulticastAddress()) {
				return false;
			}
			return true;
			
		} catch (UnknownHostException e) {
			// the name didnt resolve to an ip address
			return false;
		}
	}
}